package com.example.wechathook.hook;

import static de.robv.android.xposed.XposedBridge.hookMethod;
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
import static de.robv.android.xposed.XposedBridge.log;
import static de.robv.android.xposed.XposedHelpers.callMethod;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import static de.robv.android.xposed.XposedHelpers.newInstance;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.location.Criteria;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.SystemClock;
import android.telephony.CellIdentityCdma;
import android.telephony.CellIdentityGsm;
import android.telephony.CellIdentityLte;
import android.telephony.CellIdentityWcdma;
import android.telephony.CellInfoCdma;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
import android.telephony.CellLocation;
import android.telephony.gsm.GsmCellLocation;

import com.example.wechathook.util.PreferenceUtils;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class LocationHook extends BaseHook{

	private  boolean open;
	private  double lat;
	private  double lon;
	private  int mnc;
	private  int mcc;
	private  int lac;
	private  int cid;
	
	private  ClassLoader classLoader;

	@Override
	public void hook(XC_LoadPackage.LoadPackageParam lpp) {
		try {
			// 初始化环境
			open = PreferenceUtils.locationSwtich();
			lat = Double.parseDouble(PreferenceUtils.lat());
			lon = Double.parseDouble(PreferenceUtils.lon());
			mnc = PreferenceUtils.mnc();
			mcc = PreferenceUtils.mcc();
			lac = PreferenceUtils.lac();
			cid = PreferenceUtils.cid();
			
			classLoader = lpp.classLoader;
			
			if (!open) {
				return;
			}
			
			findAndHookMethod("android.telephony.TelephonyManager", classLoader, "getCellLocation",
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							GsmCellLocation gsmCellLocation = new GsmCellLocation();
							gsmCellLocation.setLacAndCid(lac, cid);
							param.setResult(gsmCellLocation);
						}
					});

			findAndHookMethod("android.telephony.PhoneStateListener", classLoader,
					"onCellLocationChanged", CellLocation.class, new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							GsmCellLocation gsmCellLocation = new GsmCellLocation();
							gsmCellLocation.setLacAndCid(lac, cid);
							param.setResult(gsmCellLocation);
						}
					});

			if (Build.VERSION.SDK_INT > 22) {
				findAndHookMethod("android.telephony.TelephonyManager", classLoader, "getPhoneCount",
						new XC_MethodHook() {
							@Override
							protected void afterHookedMethod(MethodHookParam param) throws Throwable {
								param.setResult(1);
							}
						});
			}

			if (Build.VERSION.SDK_INT < 23) {
				findAndHookMethod("android.telephony.TelephonyManager", classLoader,
						"getNeighboringCellInfo", new XC_MethodHook() {
							@Override
							protected void afterHookedMethod(MethodHookParam param) throws Throwable {
								param.setResult(new ArrayList<>());
							}
						});
			}

			if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
				findAndHookMethod("android.telephony.TelephonyManager", classLoader, "getAllCellInfo",
						new XC_MethodHook() {
							@Override
							protected void afterHookedMethod(MethodHookParam param) throws Throwable {
								param.setResult(getCell(mcc, mnc, lac, cid, 0, 0));
							}
						});
				findAndHookMethod("android.telephony.PhoneStateListener", classLoader,
						"onCellInfoChanged", List.class, new XC_MethodHook() {
							@Override
							protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
								param.setResult(getCell(mcc, mnc, lac, cid, 0, 0));
							}
						});
			}

			findAndHookMethod("android.net.wifi.WifiManager", classLoader, "getScanResults",
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							param.setResult(new ArrayList<>());
						}
					});

			findAndHookMethod("android.net.wifi.WifiManager", classLoader, "getWifiState",
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							param.setResult(1);
						}
					});

			findAndHookMethod("android.net.wifi.WifiManager", classLoader, "isWifiEnabled",
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							param.setResult(true);
						}
					});

			findAndHookMethod("android.net.wifi.WifiInfo", classLoader, "getMacAddress",
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							param.setResult("00-00-00-00-00-00-00-00");
						}
					});

			findAndHookMethod("android.net.wifi.WifiInfo", classLoader, "getSSID", new XC_MethodHook() {
				@Override
				protected void afterHookedMethod(MethodHookParam param) throws Throwable {
					param.setResult("null");
				}
			});

			findAndHookMethod("android.net.wifi.WifiInfo", classLoader, "getBSSID", new XC_MethodHook() {
				@Override
				protected void afterHookedMethod(MethodHookParam param) throws Throwable {
					param.setResult("00-00-00-00-00-00-00-00");
				}
			});

			findAndHookMethod("android.net.NetworkInfo", classLoader, "getTypeName", new XC_MethodHook() {
				@Override
				protected void afterHookedMethod(MethodHookParam param) throws Throwable {
					param.setResult("WIFI");
				}
			});
			findAndHookMethod("android.net.NetworkInfo", classLoader, "isConnectedOrConnecting",
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							param.setResult(true);
						}
					});

			findAndHookMethod("android.net.NetworkInfo", classLoader, "isConnected", new XC_MethodHook() {
				@Override
				protected void afterHookedMethod(MethodHookParam param) throws Throwable {
					param.setResult(true);
				}
			});

			findAndHookMethod("android.net.NetworkInfo", classLoader, "isAvailable", new XC_MethodHook() {
				@Override
				protected void afterHookedMethod(MethodHookParam param) throws Throwable {
					param.setResult(true);
				}
			});

			findAndHookMethod("android.telephony.CellInfo", classLoader, "isRegistered",
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							param.setResult(true);
						}
					});

			findAndHookMethod(LocationManager.class, "getLastLocation", new XC_MethodHook() {
				@Override
				protected void afterHookedMethod(MethodHookParam param) throws Throwable {
					Location l = new Location(LocationManager.GPS_PROVIDER);
					l.setLatitude(lat);
					l.setLongitude(lon);
					l.setAccuracy(100f);
					l.setTime(System.currentTimeMillis());
					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
						l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
					}
					param.setResult(l);
				}
			});

			findAndHookMethod(LocationManager.class, "getLastKnownLocation", String.class,
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							Location l = new Location(LocationManager.GPS_PROVIDER);
							l.setLatitude(lat);
							l.setLongitude(lon);
							l.setAccuracy(100f);
							l.setTime(System.currentTimeMillis());
							if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
								l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
							}
							param.setResult(l);
						}
					});

			hookAllMethods(LocationManager.class, "getProviders", new XC_MethodHook() {
				@Override
				protected void afterHookedMethod(MethodHookParam param) throws Throwable {
					ArrayList<String> arrayList = new ArrayList<>();
					arrayList.add("gps");
					param.setResult(arrayList);
				}
			});

			findAndHookMethod(LocationManager.class, "getBestProvider", Criteria.class, Boolean.TYPE,
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							param.setResult("gps");
						}
					});

			findAndHookMethod(LocationManager.class, "addGpsStatusListener", GpsStatus.Listener.class,
					new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							if (param.args[0] != null) {
								callMethod(param.args[0], "onGpsStatusChanged", 1);
								callMethod(param.args[0], "onGpsStatusChanged", 3);
							}
						}
					});

			findAndHookMethod(LocationManager.class, "addNmeaListener", GpsStatus.NmeaListener.class,
					new XC_MethodHook() {
						@Override
						protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
							param.setResult(false);
						}
					});

			findAndHookMethod("android.location.LocationManager", classLoader, "getGpsStatus",
					GpsStatus.class, new XC_MethodHook() {
						@Override
						protected void afterHookedMethod(MethodHookParam param) throws Throwable {
							GpsStatus gss = (GpsStatus) param.getResult();
							if (gss == null)
								return;

							Class<?> clazz = GpsStatus.class;
							Method m = null;
							for (Method method : clazz.getDeclaredMethods()) {
								if (method.getName().equals("setStatus")) {
									if (method.getParameterTypes().length > 1) {
										m = method;
										break;
									}
								}
							}
							if (m == null)
								return;

							// access the private setStatus function of
							// GpsStatus
							m.setAccessible(true);

							// make the apps belive GPS works fine now
							int svCount = 5;
							int[] prns = { 1, 2, 3, 4, 5 };
							float[] snrs = { 0, 0, 0, 0, 0 };
							float[] elevations = { 0, 0, 0, 0, 0 };
							float[] azimuths = { 0, 0, 0, 0, 0 };
							int ephemerisMask = 0x1f;
							int almanacMask = 0x1f;

							// 5 satellites are fixed
							int usedInFixMask = 0x1f;

							callMethod(gss, "setStatus", svCount, prns, snrs, elevations, azimuths,
									ephemerisMask, almanacMask, usedInFixMask);
							param.args[0] = gss;
							param.setResult(gss);
							try {
								m.invoke(gss, svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask,
										usedInFixMask);
								param.setResult(gss);
							} catch (Exception e) {
								log(e);
							}
						}
					});

			for (Method method : LocationManager.class.getDeclaredMethods()) {
				if (method.getName().equals("requestLocationUpdates") && !Modifier.isAbstract(method.getModifiers())
						&& Modifier.isPublic(method.getModifiers())) {
					XposedBridge.hookMethod(method, new XC_MethodHook() {
						@Override
						protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
							if (param.args.length >= 4 && (param.args[3] instanceof LocationListener)) {

								LocationListener ll = (LocationListener) param.args[3];

								Class<?> clazz = LocationListener.class;
								Method m = null;
								for (Method method : clazz.getDeclaredMethods()) {
									if (method.getName().equals("onLocationChanged")
											&& !Modifier.isAbstract(method.getModifiers())) {
										m = method;
										break;
									}
								}
								Location l = new Location(LocationManager.GPS_PROVIDER);
								l.setLatitude(lat);
								l.setLongitude(lon);
								l.setAccuracy(10.00f);
								l.setTime(System.currentTimeMillis());
								if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
									l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
								}
								callMethod(ll, "onLocationChanged", l);
								try {
									if (m != null) {
										m.invoke(ll, l);
									}
								} catch (Exception e) {
									XposedBridge.log(e);
								}
							}
						}
					});
				}

				if (method.getName().equals("requestSingleUpdate ") && !Modifier.isAbstract(method.getModifiers())
						&& Modifier.isPublic(method.getModifiers())) {
					hookMethod(method, new XC_MethodHook() {
						@Override
						protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
							if (param.args.length >= 3 && (param.args[1] instanceof LocationListener)) {

								LocationListener ll = (LocationListener) param.args[3];

								Class<?> clazz = LocationListener.class;
								Method m = null;
								for (Method method : clazz.getDeclaredMethods()) {
									if (method.getName().equals("onLocationChanged")
											&& !Modifier.isAbstract(method.getModifiers())) {
										m = method;
										break;
									}
								}

								try {
									if (m != null) {
										Location l = new Location(LocationManager.GPS_PROVIDER);
										l.setLatitude(lat);
										l.setLongitude(lon);
										l.setAccuracy(100f);
										l.setTime(System.currentTimeMillis());
										if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
											l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
										}
										m.invoke(ll, l);
									}
								} catch (Exception e) {
									XposedBridge.log(e);
								}
							}
						}
					});
				}
			}
		} catch (Throwable e) {

		}
	}
	
	 /**
     * 
     * @param mcc 国家代码，中国460
     * @param mnc 移动网络号码（中国移动为0，中国联通为1，中国电信为2）
     * @param lac 位置区域码
     * @param cid 基站编号
     * @param sid 电信,同mnc
     * @param networkType
     * @return
     */
	@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
	private  ArrayList getCell(int t_mcc, int t_mnc, int t_lac, int t_cid, int t_sid, int networkType) {
        ArrayList arrayList = new ArrayList();
        CellInfoGsm cellInfoGsm = (CellInfoGsm) newInstance(CellInfoGsm.class);
        callMethod(cellInfoGsm, "setCellIdentity", newInstance(CellIdentityGsm.class, new Object[]{Integer.valueOf(t_mcc), Integer.valueOf(t_mnc), Integer.valueOf(
        		t_lac), Integer.valueOf(t_cid)}));
        CellInfoCdma cellInfoCdma = (CellInfoCdma) newInstance(CellInfoCdma.class);
        callMethod(cellInfoCdma, "setCellIdentity", newInstance(CellIdentityCdma.class, new Object[]{Integer.valueOf(t_lac), Integer.valueOf(t_sid), Integer.valueOf(t_cid), Integer.valueOf(0), Integer.valueOf(0)}));
        CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) newInstance(CellInfoWcdma.class);
        callMethod(cellInfoWcdma, "setCellIdentity", newInstance(CellIdentityWcdma.class, new Object[]{Integer.valueOf(t_mcc), Integer.valueOf(t_mnc), Integer.valueOf(t_lac), Integer.valueOf(t_cid), Integer.valueOf(300)}));
        CellInfoLte cellInfoLte = (CellInfoLte) newInstance(CellInfoLte.class);
        callMethod(cellInfoLte, "setCellIdentity", newInstance(CellIdentityLte.class, new Object[]{Integer.valueOf(t_mcc), Integer.valueOf(t_mnc), Integer.valueOf(t_cid), Integer.valueOf(300), Integer.valueOf(t_lac)}));
        if (networkType == 1 || networkType == 2) {
            arrayList.add(cellInfoGsm);
        } else if (networkType == 13) {
            arrayList.add(cellInfoLte);
        } else if (networkType == 4 || networkType == 5 || networkType == 6 || networkType == 7 || networkType == 12 || networkType == 14) {
            arrayList.add(cellInfoCdma);
        } else if (networkType == 3 || networkType == 8 || networkType == 9 || networkType == 10 || networkType == 15) {
            arrayList.add(cellInfoWcdma);
        }
        return arrayList;
    }
}
